#include "loginoptionswidget.h"

#include <pwd.h>
#include <unistd.h>

#include <opencv2/opencv.hpp>
#include "giodbus.h"
#include "biometricdeviceinfo.h"

#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QButtonGroup>
#include <QToolButton>
#include <QTimer>
#include <QPainter>
#include <QtSvg/QSvgRenderer>
#include <QImage>
#include <QApplication>

LoginOptionsWidget::LoginOptionsWidget(BiometricProxy* proxy, int uid, QWidget *parent)
    : QWidget(parent)
    , m_biomericProxy(proxy)
    , m_uid(uid)
{
    initUI();
    initConnections();
    m_mapDisableDev.clear();
}

LoginOptionsWidget::~LoginOptionsWidget()
{

}

void LoginOptionsWidget::initUI()
{
    this->setFixedHeight(86);
    // 初始化UI
    m_layoutMain = new QVBoxLayout();
    m_layoutMain->setContentsMargins(0,0,0,0);
    m_layoutMain->setSpacing(16);
    m_layoutOptBtns = new QHBoxLayout();
    m_layoutOptBtns->setContentsMargins(0,0,0,0);
    m_layoutOptBtns->setSpacing(16);

    m_labelOptTitle = new QLabel();
    m_labelOptTitle->setAlignment(Qt::AlignCenter);
    m_labelOptTitle->setText(tr("Login Options"));
    m_labelOptTitle->setFixedHeight(22);
    m_layoutMain->addWidget(m_labelOptTitle);

    m_btnGroup = new QButtonGroup(this);
    m_btnGroup->setExclusive(true);

    m_layoutOptBtns->setAlignment(Qt::AlignCenter);
    m_layoutMain->addLayout(m_layoutOptBtns);

    m_layoutMain->addStretch();
    this->setLayout(m_layoutMain);
}

void LoginOptionsWidget::initConnections()
{
    if(m_biomericProxy && m_biomericProxy->isValid())
    {
        connect(m_biomericProxy, &BiometricProxy::StatusChanged,
                this, &LoginOptionsWidget::onStatusChanged);

        connect(m_biomericProxy, &BiometricProxy::FrameWritten,
                this, &LoginOptionsWidget::onFrameWritten);
        connect(m_biomericProxy, &BiometricProxy::USBDeviceHotPlug,
                this, &LoginOptionsWidget::onUSBDeviceHotPlug);
        readDevicesInfo();
    }
    connect(m_btnGroup, SIGNAL(buttonClicked(int)), this, SLOT(onOptionSelected(int)));
}

bool LoginOptionsWidget::getCurLoginOpt(int& nLoginOptType, int& nDrvId)
{
    if (m_curDevInfo) {
        nLoginOptType = convertDeviceType(m_curDevInfo->deviceType);
        nDrvId = m_curDevInfo->id;
        return true;
    }
    return false;
}

unsigned LoginOptionsWidget::getLoginOptCount()
{
    return m_mapDevices.size();
}

DeviceInfoPtr LoginOptionsWidget::getFirstDevInfo()
{
    DeviceInfoPtr devInfo = nullptr;
    int nDrvId = GetLastDevice(getpwuid(m_uid)->pw_name);
    if (nDrvId >= 0) {
        qDebug()<<"GetLastDevice:"<<nDrvId;
        DeviceMap::iterator itDevInfo = m_mapDevices.begin();
        for (; itDevInfo != m_mapDevices.end(); itDevInfo++) {
            for (auto devinfo : itDevInfo.value()) {
                if (devinfo && devinfo->id == nDrvId) {
                    if (!isDeviceDisable(devinfo->id)) {
                        devInfo =  devinfo;
                        break;
                    }
                }
            }
            if (devInfo) {
                break;
            }
        }
    }
    if (!devInfo) {
        DeviceMap::iterator itDevInfo = m_mapDevices.begin();
        for (; itDevInfo != m_mapDevices.end(); itDevInfo++) {
            for (auto devinfo : itDevInfo.value()) {
                if (devinfo && !isDeviceDisable(devinfo->id)) {
                    devInfo =  devinfo;
                    break;
                }
            }
            if (devInfo) {
                break;
            }
        }
    }
    return devInfo;
}

void LoginOptionsWidget::addOptionButton(unsigned uLoginOptType, int nDrvId, QString strDrvName)
{
    if (m_mapOptBtns.contains(nDrvId)) {
        return ;
    }
    QToolButton *newButton = new QToolButton();
    QVBoxLayout *layoutBtn = new QVBoxLayout();
    newButton->setLayout(layoutBtn);
    QLabel *newLabel = new QLabel();
    layoutBtn->setAlignment(Qt::AlignCenter);
    layoutBtn->addWidget(newLabel);
    newButton->setCheckable(true);
    newButton->setChecked(false);
    newButton->setFocusPolicy(Qt::NoFocus);
    if (nDrvId == -1) {
        //newButton->setEnabled(false);
        newButton->setChecked(true);
        //屏蔽鼠标事件
        newButton->setAttribute(Qt::WA_TransparentForMouseEvents, true);
    } else {
       int nLength = m_btnGroup->buttons().length();
       m_btnGroup->addButton(newButton, nLength);
       m_listDriveId.append(nDrvId);
    }
    QPixmap iconPixmap;
    switch (uLoginOptType) {
    case LOGINOPT_TYPE_PASSWORD:
        iconPixmap = loadSvg(":/image/assets/ukui-loginopt-password.svg", "white", 16);
        break;
    case LOGINOPT_TYPE_FACE:
        iconPixmap = loadSvg(":/image/assets/ukui-loginopt-face.svg", "white", 16);
        break;
    case LOGINOPT_TYPE_FINGERPRINT:
        iconPixmap = loadSvg(":/image/assets/ukui-loginopt-finger.svg", "white", 16);
        break;
    case LOGINOPT_TYPE_IRIS:
        iconPixmap = loadSvg(":/image/assets/ukui-loginopt-iris.svg", "white", 16);
        break;
    case LOGINOPT_TYPE_VOICEPRINT:
        iconPixmap = loadSvg(":/image/assets/ukui-loginopt-voice.svg", "white", 16);
        break;
    case LOGINOPT_TYPE_FINGERVEIN:
        iconPixmap = loadSvg(":/image/assets/ukui-loginopt-fingervein.svg", "white", 16);
        break;
    case LOGINOPT_TYPE_QRCODE:
        iconPixmap = loadSvg(":/image/assets/ukui-loginopt-qrcode.svg", "white", 16);
        break;
    }
    newLabel->setPixmap(iconPixmap);
    newButton->setToolTip(strDrvName);
    newButton->setStyleSheet("QToolTip{border-radius:4px;background-color:#FFFFFF;color:black;font-size:16px}");
    newButton->setFixedSize(48, 48);
    if (isDeviceDisable(nDrvId)) {
        newButton->setDisabled(true);
    } else {
        newButton->setDisabled(false);
    }

    m_layoutOptBtns->addWidget(newButton);
    m_mapOptBtns[nDrvId] = newButton;
}

void LoginOptionsWidget::clearOptionButtons()
{
    QMap<int, QToolButton*>::iterator itMapBtn = m_mapOptBtns.begin();
    for ( ; itMapBtn != m_mapOptBtns.end(); itMapBtn++) {
        if (itMapBtn.value()) {
            m_btnGroup->removeButton(itMapBtn.value());
            m_layoutOptBtns->removeWidget(itMapBtn.value());
            itMapBtn.value()->deleteLater();
        }
    }
    m_listDriveId.clear();
    m_mapOptBtns.clear();
}

void LoginOptionsWidget::updateOptionButtons()
{
    clearOptionButtons();
    //addOptionButton(LOGINOPT_TYPE_PASSWORD, -1, tr("Password"));
    DeviceMap::iterator itMapDev = m_mapDevices.begin();
    for ( ; itMapDev != m_mapDevices.end(); itMapDev++) {
        for (DeviceInfoPtr devPtr : itMapDev.value()) {
            if (devPtr) {
                addOptionButton(itMapDev.key(), devPtr->id, DeviceType::getDeviceType_tr(devPtr->deviceType));
            }
        }
    }
    qDebug()<<"m_mapOptBtns.size():"<<m_mapOptBtns.size();
    if (m_mapOptBtns.size() <= 1) {
        m_labelOptTitle->hide();
        QMap<int, QToolButton*>::iterator itMapBtn = m_mapOptBtns.begin();
        for ( ; itMapBtn != m_mapOptBtns.end(); itMapBtn++) {
            if (itMapBtn.value()) {
                itMapBtn.value()->hide();
            }
        }
    } else {
        m_labelOptTitle->show();
        QMap<int, QToolButton*>::iterator itMapBtn = m_mapOptBtns.begin();
        for ( ; itMapBtn != m_mapOptBtns.end(); itMapBtn++) {
            if (itMapBtn.value()) {
                itMapBtn.value()->show();
            }
        }
    }
}

void LoginOptionsWidget::setUser(int uid)
{
    m_uid = uid;
    readDevicesInfo();
}

void LoginOptionsWidget::readDevicesInfo()
{
    m_mapDevices.clear();
    bool isAuthEnable = GetAuthEnable();
    bool isQRCodeEnable = GetQRCodeEnable();
    DeviceList deviceList = m_biomericProxy->GetDevList();
    for(auto pDeviceInfo : deviceList)
    {
        qDebug()<<"BeginGetFeature------!";
        int nDevFeatureCount = m_biomericProxy->GetUserDevFeatureCount(m_uid,pDeviceInfo->id);
        qDebug() << *pDeviceInfo << ",feautres:"<<nDevFeatureCount;
        if(nDevFeatureCount > 0) {
            if (!isAuthEnable && pDeviceInfo->deviceType <= DeviceType::VoicePrint)
                continue;
            if (!isQRCodeEnable && pDeviceInfo->deviceType == REMOTE_QRCODE_TYPE)
                continue;
            int nDevType = LOGINOPT_TYPE_OTHERS;
            nDevType = convertDeviceType(pDeviceInfo->deviceType);
            m_mapDevices[nDevType].push_back(pDeviceInfo);
        }
    }
    updateOptionButtons();
}

void LoginOptionsWidget::startAuth(DeviceInfoPtr device, int uid)
{
    if(!m_biomericProxy)
    {
        qWarning() << "BiometricProxy doesn't exist.";
        return;
    }
    if (!device) {
        qWarning() << "Biometric Auth device invalid.";
        return ;
    }

    if(m_isInAuth)
    {
        qDebug() << "Identification is currently under way, stop it";
        stopAuth();
    }
    qDebug()<<"deviceInfo:"<<device->id;
    this->m_curDevInfo = device;
    this->m_uid = uid;
    this->m_strUserName = getpwuid(uid)->pw_name;
    this->m_isStopped = false;
    this->m_curLoginOptType = convertDeviceType(this->m_curDevInfo->deviceType);
    updateUIStatus();
    SetLastDevice(this->m_strUserName, this->m_curDevInfo->id);
    startAuth_();
}

void LoginOptionsWidget::startAuth_()
{
    if (!m_curDevInfo)
        return ;
    qDebug().noquote() << QString("Identify:[drvid: %1, uid: %2]").arg(m_curDevInfo->id).arg(m_uid);

    m_isInAuth = true;
    m_dupFD = -1;

    QDBusPendingCall call = m_biomericProxy->Identify(m_curDevInfo->id, m_uid);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
    connect(watcher, &QDBusPendingCallWatcher::finished,
            this, &LoginOptionsWidget::onIdentifyComplete);
}

void LoginOptionsWidget::stopAuth()
{
    m_isStopped = true;
    if(!m_isInAuth || !m_curDevInfo)
    {
        return;
    }
    m_biomericProxy->StopOps(m_curDevInfo->id);
    if(m_retrytimer&&m_retrytimer->isActive()){
        m_retrytimer->stop();
        delete m_retrytimer;
        m_retrytimer = nullptr;
    }
    m_isInAuth = false;
    Q_EMIT updateImage(QImage());
}

void LoginOptionsWidget::onIdentifyComplete(QDBusPendingCallWatcher *watcher)
{
    if(m_isStopped == true)
        return ;

    QDBusPendingReply<int, int> reply = *watcher;
    if(reply.isError())
    {
        qWarning() << "Identify error: " << reply.error().message();
        Q_EMIT authComplete(false, -1);
        return;
    }
    int result = reply.argumentAt(0).toInt();
    int authUid = reply.argumentAt(1).toInt();
    qDebug() << result << authUid << m_uid;

    // 特征识别成功，而且用户id匹配
    if(result == DBUS_RESULT_SUCCESS && authUid == m_uid) {
        qDebug() << "Identify success";
        Q_EMIT authComplete(true, 0);
    } else if(result == DBUS_RESULT_NOTMATCH) { // 特征识别不匹配
        qDebug() << "Identify failed";
        Q_EMIT authComplete(false, 2);
    } else if(result == DBUS_RESULT_ERROR) {  //识别发生错误
        StatusReslut ret = m_biomericProxy->UpdateStatus(m_curDevInfo->id);
        qDebug()<<"StatusReslut:"<<ret.result<<","<<ret.enable<<","<<ret.devNum<<","
                  <<ret.devStatus<<","<<ret.opsStatus<<","<<ret.notifyMessageId;
        //识别操作超时 304/404 认证超时；8 网络错误；
        if(ret.result == 0) // 304认证超时， 8网络异常
        {
            if (ret.opsStatus == OPS_IDENTIFY_TIMEOUT || ret.opsStatus == OPS_VERIFY_TIMEOUT
                || ret.opsStatus == 8) {    // 304认证超时， 8网络异常
                Q_EMIT authComplete(false, 1);
            } else if (ret.opsStatus == OPS_IDENTIFY_STOP_BY_USER || ret.opsStatus == OPS_VERIFY_STOP_BY_USER) {
                Q_EMIT authComplete(false, -2); // 主动停止，直接重试
            } else {
                Q_EMIT authComplete(false, 2);
            }
        } else {
            Q_EMIT authComplete(false, 2);
        }
    } else {
        Q_EMIT authComplete(false, 2);
    }
}

void LoginOptionsWidget::onFrameWritten(int drvid)
{
    if ((m_curDevInfo && m_curDevInfo->id != drvid) || !m_isInAuth) {
        return ;
    }
    if(m_dupFD == -1){
        m_dupFD = get_server_gvariant_stdout(drvid);
    }

    if(m_dupFD <= 0)
        return ;

    cv::Mat img;
    lseek(m_dupFD, 0, SEEK_SET);
    char base64_bufferData[1024*1024];
    int rc = read(m_dupFD, base64_bufferData, 1024*1024);
    printf("rc = %d\n", rc);

    cv::Mat mat2(1, sizeof(base64_bufferData), CV_8U, base64_bufferData);
    img = cv::imdecode(mat2, cv::IMREAD_COLOR);
    if (!img.data)
        return ;
    cv::cvtColor(img,img,cv::COLOR_BGR2RGB);

    QImage srcQImage = QImage((uchar*)(img.data), img.cols, img.rows, QImage::Format_RGB888);
    Q_EMIT updateImage(srcQImage);
}

void LoginOptionsWidget::onStatusChanged(int drvid, int status)
{
    if(!m_isInAuth || !m_curDevInfo)
    {
        return;
    }
    if(drvid != m_curDevInfo->id)
    {
        return;
    }

//    // 显示来自服务的提示信息
//    if(status == STATUS_NOTIFY)
//    {
//        QString notifyMsg = m_biomericProxy->GetNotifyMesg(drvid);
//        Q_EMIT updateAuthMsg(notifyMsg);
//    }
}

void LoginOptionsWidget::setCurrentDevice(int drvid)
{
    DeviceInfoPtr pDeviceInfo = findDeviceById(drvid);
    if(pDeviceInfo)
    {
        setCurrentDevice(pDeviceInfo);
    }
}

void LoginOptionsWidget::setCurrentDevice(const QString &deviceName)
{
    DeviceInfoPtr pDeviceInfo = findDeviceByName(deviceName);
    if(pDeviceInfo)
    {
        setCurrentDevice(pDeviceInfo);
    }
}

void LoginOptionsWidget::setCurrentDevice(const DeviceInfoPtr &pDeviceInfo)
{
    this->m_curDevInfo = pDeviceInfo;
}

DeviceInfoPtr LoginOptionsWidget::findDeviceById(int drvid)
{
    for(int type : m_mapDevices.keys())
    {
        DeviceList &deviceList = m_mapDevices[type];
        auto iter = std::find_if(deviceList.begin(), deviceList.end(),
                                 [&](DeviceInfoPtr ptr){
            return ptr->id == drvid;
        });
        if(iter != deviceList.end())
        {
            return *iter;
        }
    }
    return DeviceInfoPtr();
}

DeviceInfoPtr LoginOptionsWidget::findDeviceByName(const QString &name)
{
    for(int type : m_mapDevices.keys())
    {
        DeviceList &deviceList = m_mapDevices[type];
        auto iter = std::find_if(deviceList.begin(), deviceList.end(),
                                 [&](DeviceInfoPtr ptr){
            return ptr->shortName == name;
        });
        if(iter != deviceList.end())
        {
            return *iter;
        }
    }
    return DeviceInfoPtr();
}

void LoginOptionsWidget::onUSBDeviceHotPlug(int drvid, int action, int /*devNum*/)
{
    int savedDeviceId = (m_curDevInfo ? m_curDevInfo->id : -1);
    int savedCount = 0;
    for(int type : m_mapDevices.keys())
        savedCount += m_mapDevices.value(type).count();

    switch(action)
    {
    case ACTION_ATTACHED:
    {
        //插入设备后，需要更新设备列表
        readDevicesInfo();
        break;
    }
    case ACTION_DETACHED:
    {
        DeviceInfoPtr pDeviceInfo = findDeviceById(drvid);
        if(pDeviceInfo)
        {
            int nDevType = LOGINOPT_TYPE_PASSWORD;
            nDevType = convertDeviceType(pDeviceInfo->deviceType);
            if (savedDeviceId == drvid) {
                if (m_isInAuth) {
                    Q_EMIT updateAuthMsg(tr("Identify device removed!"));
                }
            }
            m_mapDevices[nDevType].removeOne(pDeviceInfo);
            if(m_mapDevices[nDevType].isEmpty()) {
                m_mapDevices.remove(nDevType);
            }
        }
        break;
    }
    }

    int count = 0;
    for(int type : m_mapDevices.keys())
        count += m_mapDevices.value(type).count();

    //设备数量发生了变化
    if(count != savedCount) {
        updateOptionButtons();
        Q_EMIT notifyOptionsChange(count);
    }
}

bool LoginOptionsWidget::getAuthDouble()
{
    QSettings settings("/etc/biometric-auth/ukui-biometric.conf", QSettings::IniFormat);
    bool distribId = settings.value("DoubleAuth").toBool();
    return distribId;
}

void LoginOptionsWidget::updateUIStatus()
{
    if (m_mapOptBtns.contains(-1)) {
        QToolButton* btn = m_mapOptBtns[-1];
        if (btn) {
            btn->setChecked(true);
        }
    }
    if (m_curDevInfo) {
        if (m_mapOptBtns.contains(m_curDevInfo->id)) {
            QToolButton* btn = m_mapOptBtns[m_curDevInfo->id];
            if (btn) {
                btn->setChecked(true);
            }
        }
    }
}

void LoginOptionsWidget::onOptionSelected(int nIndex)
{
    if (nIndex < 0)
        return;
    if (nIndex < m_listDriveId.size()) {
        DeviceInfoPtr info = findDeviceById(m_listDriveId[nIndex]);
        if (info && !isDeviceDisable(info->id)) {
            Q_EMIT optionSelected(convertDeviceType(info->deviceType), info);
        }
    }
}

int LoginOptionsWidget::convertDeviceType(int nDevType)
{
    int nLoginOptType = LOGINOPT_TYPE_OTHERS;
    switch (nDevType) {
    case BioT_FingerPrint:
        nLoginOptType = LOGINOPT_TYPE_FINGERPRINT;
        break;
    case BioT_FingerVein:
        nLoginOptType = LOGINOPT_TYPE_FINGERVEIN;
        break;
    case BioT_Iris:
        nLoginOptType = LOGINOPT_TYPE_IRIS;
        break;
    case BioT_Face:
        nLoginOptType = LOGINOPT_TYPE_FACE;
        break;
    case BioT_VoicePrint:
        nLoginOptType = LOGINOPT_TYPE_VOICEPRINT;
        break;
    case UniT_Remote:
        nLoginOptType = LOGINOPT_TYPE_QRCODE;
        break;
    default:
        nLoginOptType = LOGINOPT_TYPE_OTHERS;
        break;
    }
    return nLoginOptType;
}

void LoginOptionsWidget::setDeviceDisable(int nDevId, bool bDisable)
{
    if (bDisable) {
        m_mapDisableDev[m_uid][nDevId] = true;
        QMap<int, QToolButton*>::iterator itMapBtn = m_mapOptBtns.begin();
        for ( ; itMapBtn != m_mapOptBtns.end(); itMapBtn++) {
            if (itMapBtn.key() == nDevId && itMapBtn.value()) {
                itMapBtn.value()->setDisabled(true);
                break;
            }
        }
    } else {
        m_mapDisableDev[m_uid][nDevId] = false;
        QMap<int, QToolButton*>::iterator itMapBtn = m_mapOptBtns.begin();
        for ( ; itMapBtn != m_mapOptBtns.end(); itMapBtn++) {
            if (itMapBtn.key() == nDevId && itMapBtn.value()) {
                itMapBtn.value()->setDisabled(false);
                break;
            }
        }
    }
}

bool LoginOptionsWidget::isDeviceDisable(int nDevId)
{
    if (m_mapDisableDev[m_uid].contains(nDevId)) {
        return m_mapDisableDev[m_uid][nDevId];
    }
    return false;
}

QPixmap LoginOptionsWidget::loadSvg(QString path, QString color, int size)
{
    int origSize = size;
    const auto ratio = qApp->devicePixelRatio();
    if ( 2 == ratio) {
        size += origSize;
    } else if (3 == ratio) {
        size += origSize;
    }
    QPixmap pixmap(size, size);
    QSvgRenderer renderer(path);
    pixmap.fill(Qt::transparent);

    QPainter painter;
    painter.begin(&pixmap);
    renderer.render(&painter);
    painter.end();

    pixmap.setDevicePixelRatio(ratio);
    return drawSymbolicColoredPixmap(pixmap, color);
}

QPixmap LoginOptionsWidget::drawSymbolicColoredPixmap(QPixmap &source, QString cgColor)
{
    QImage img = source.toImage();
    for (int x = 0; x < img.width(); x++) {
        for (int y = 0; y < img.height(); y++) {
            auto color = img.pixelColor(x, y);
            if (color.alpha() > 0) {
                if ( "white" == cgColor) {
                    color.setRed(255);
                    color.setGreen(255);
                    color.setBlue(255);
                    img.setPixelColor(x, y, color);
                } else if( "black" == cgColor) {
                    color.setRed(0);
                    color.setGreen(0);
                    color.setBlue(0);
                    img.setPixelColor(x, y, color);
                } else if ("gray"== cgColor) {
                    color.setRed(152);
                    color.setGreen(163);
                    color.setBlue(164);
                    img.setPixelColor(x, y, color);
                } else if ("blue" == cgColor){
                    color.setRed(61);
                    color.setGreen(107);
                    color.setBlue(229);
                    img.setPixelColor(x, y, color);
                } else {
                    return source;
                }
            }
        }
    }
    return QPixmap::fromImage(img);
}
